#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <errno.h>
#include <string.h>

/**
struct dirent {
	ino_t d_ino;  // i节点号
	off_t d_off;  // 在目录文件中的偏移
	usigned short d_reclen; // 文件名长度
	unsigned char d_type;  // 文件类型
	char d_name[256];  // 文件名，重要
};
**/

/**
struct stat {
	mode_t st_mode; // 文件类型与访问权限，重要
	ino_t st_ino;  // i节点号
	dev_t st_dev;  // 文件使用的设备号
	dev_t st_rdev; // 设备文件的设备号
	nlink_t st_nlink; // 文件的硬链接数，重要
	uid_t st_uid;  // 文件所有者用户ID，重要
	gid_t st_gid;  // 文件所有者组ID，重要
	off_t st_size; // 文件大小（以字节为单位），重要
	time_t st_atime;  // 最后一次访问该文件的时间
	time_t st_mtime;  // 最后一次修改该文件的时间，重要
	time_t st_ctime;  // 最后一次改变该文件状态的时间
	blksize_t st_blksize; // 包含该文件的磁盘块的大小
	blkcnt_t st_blocks;  // 该文件所占的磁盘块数
};
**/

/* 打印文件类型和权限 */
void print_perm(struct stat st) {
    char str[10] = {"----------"};

    if (S_ISDIR(st.st_mode)) str[0] = 'd';  // 目录文件
    if (S_ISCHR(st.st_mode)) str[0] = 'c';  // 字符特殊文件
    if (S_ISBLK(st.st_mode)) str[0] = 'b';  // 块特殊文件
    if (S_ISFIFO(st.st_mode)) str[0] = 'p'; // FIFO或管道
    if (S_ISLNK(st.st_mode)) str[0] = 'l';  // 符号链接
    if (S_ISSOCK(st.st_mode)) str[0] = 's'; // 套接字文件

    if ((S_IRUSR & st.st_mode) == S_IRUSR) str[1] = 'r';
    if ((S_IWUSR & st.st_mode) == S_IWUSR) str[2] = 'w';
    if ((S_IXUSR & st.st_mode) == S_IXUSR) str[3] = 'x';

    if ((S_IRGRP & st.st_mode) == S_IRGRP) str[4] = 'r';
    if ((S_IWGRP & st.st_mode) == S_IWGRP) str[5] = 'w';
    if ((S_IXGRP & st.st_mode) == S_IXGRP) str[6] = 'x';

    if ((S_IROTH & st.st_mode) == S_IROTH) str[7] = 'r';
    if ((S_IWOTH & st.st_mode) == S_IWOTH) str[8] = 'w';
    if ((S_IXOTH & st.st_mode) == S_IXOTH) str[9] = 'x';

    printf("%s", str);
}

/* 打印文件硬链接数或目录子目录数 */
void print_link(struct stat st) {
    printf(" %2d", (int)st.st_nlink);
}

/* 打印文件所有者 */
void print_usrname(struct stat st) {
    printf(" %s", getpwuid(st.st_uid)->pw_name);
}

/* 打印文件所有者所在组 */
void print_grname(struct stat st) {
    printf(" %s", getgrgid(st.st_gid)->gr_name);
}

/* 打印文件大小 */
void print_fsize(struct stat st) {
    printf(" %5ld", st.st_size);
}

/* 打印文件最后修改时间 */
void print_time(struct stat st) {
    // printf(" %s", ctime(&st.st_mtime));  // 会被自动换行，无语
    struct tm* lchangetime = localtime(&(st.st_mtime));
    int mon = lchangetime->tm_mon + 1;
    char monstr[5] = {0};
    switch (mon) {
        case 1: strcpy(monstr, "Jan"); break;
        case 2: strcpy(monstr, "Feb"); break;
        case 3: strcpy(monstr, "Mar"); break;
        case 4: strcpy(monstr, "Apr"); break;
        case 5: strcpy(monstr, "May"); break;
        case 6: strcpy(monstr, "Jun"); break;
        case 7: strcpy(monstr, "Jul"); break;
        case 8: strcpy(monstr, "Aug"); break;
        case 9: strcpy(monstr, "Sept"); break;
        case 10: strcpy(monstr, "Oct"); break;
        case 11: strcpy(monstr, "Nov"); break;
        default: strcpy(monstr, "Dec");
    }

    printf(" %s %2d %02d:%02d ", monstr, lchangetime->tm_mday, lchangetime->tm_hour, lchangetime->tm_min);
}

/* 打印文件名组 */
void print_filename(int mode, int uid, char *fname) {
    if (S_ISDIR(mode)) {
        // 目录文件，显示为蓝色
        printf("\33[1;34m%s\033[0m\n", fname);
    } else if (S_ISREG(mode)) {
        if(mode & S_IXUSR || mode & S_IXGRP || mode & S_IXOTH) {
            if(uid == 0) // 属主用户，文件名显示为红色
                printf("\33[41;37m%s\033[0m\n", fname);
            else  // 其他用户，文件名显示为绿色
                printf("\33[1;32m%s\033[0m\n", fname);
        } else {
            printf("%s\n", fname);
        }
    } else {
        printf("%s\n", fname);
    }
}

int main(int argc, char *argv[]) {
    char curdir_show;
    char hidden_show;
    int stat_type = -1;

    printf("Do you want to display the current directory (.) and parent directory (..)?(Y/N)\n");
    scanf("%c", &curdir_show);

    printf("Do you want to display hidden files?(Y/N)\n");
    scanf(" %c", &hidden_show);

    printf("Please choose: \n\
    1) Display the attributes of the symbolic link file itself. \n\
    2) Display the file attributes of the file that the symbolic link points to.\n");
    scanf("%d", &stat_type);

    char buf[255];
    DIR *currentdir;
    struct dirent *currentdp;
    struct stat currentstat;

    /* 获取当前目录路径 */
    getcwd(buf, sizeof(buf));

    /* 打开当前目录，判断是否成功 */
    currentdir = opendir(buf);
    if (currentdir == NULL) {
        printf("myls: No such file or directory\n");
        exit(0);
    }

    /**
     * 读取当前目录下的目录项，并判断是否成功 
     * readdir 返回一个dirent结构体，实验中用到是的目录名字段 d_name
    **/
    while ((currentdp = readdir(currentdir)) != NULL) {
        
        /* 是否显示当前目录和父目录 */
        if ((curdir_show == 'N' || curdir_show == 'n') && (strcmp(currentdp->d_name, ".") == 0 || strcmp(currentdp->d_name, "..") == 0)) {
            continue;
        }

        /* 是否显示隐藏文件，当文件名第一个字符是.时，为隐藏文件*/
        if ((hidden_show == 'N' || hidden_show == 'n') && (int)strlen(currentdp->d_name) >= 2 && strcmp(currentdp->d_name, "..") != 0 && currentdp->d_name[0] == '.') {
            continue;
        }

        char temp[128] = {0};
        strcpy(temp, buf);
        strcat(temp, "/");

        /* 读取当前文件的信息，并判断是否成功 */
        strcat(temp, currentdp->d_name);  // 将char的指针指向string需要使用c_str函数
        
        if ((stat_type == 2) && (stat(temp, &currentstat) != 0)) {
            printf("get stat error\n");
            continue;
        }

        if ((stat_type == 1) && (lstat(temp, &currentstat) != 0)) {
            printf("get stat error\n");
            continue;
        }

        print_perm(currentstat);
        print_link(currentstat);
        print_usrname(currentstat);
        print_grname(currentstat);
        print_fsize(currentstat);
        print_time(currentstat);
        print_filename(currentstat.st_mode, currentstat.st_uid, currentdp->d_name);
    }

    closedir(currentdir);

    return 0;
}